Add asset images, AsyncImage, and content modes - #53
Merged
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Imagepreviously resolved only a curated ~25-symbol Material map; anything else rendered as a[name]placeholder, and there was no way to show a remote image. This closes that gap.What's new
Image("name")resolves against the app's drawable resources (the Android analog of an asset-catalog lookup), falling back to the existing placeholder when there's no match.Image.resizable()plus.scaledToFit()/.scaledToFill()/.aspectRatio(contentMode:).resizable()is a method onImageitself, as in SwiftUI; without it an image keeps its natural size.AsyncImage(url:)— fetch, decode, and swap in, entirely Compose-side: a spinner while loading, a labeled placeholder on failure. Loading never touches the bridge, and the effect is keyed by URL so a new URL reloads.Both loaders sit behind an
expect/actualseam (rememberAssetPainter,loadRemoteImage), mirroring howVideoPlayeris structured, so the common interpreter stays free of platform APIs.A bug the device caught
My first loader used
URL.openStream(), and the real remote image failed while the deliberately-invalid one "passed".openStream()sends the defaultJava/<version>user agent, which a number of CDNs reject outright — I confirmed Wikimedia answers 403 to it. It also doesn't follow cross-protocol redirects or treat a non-200 as failure, so an error page would have been decoded as garbage. Replaced with an explicitHttpURLConnectionthat sets a realUser-AgentandAccept, follows up to 5 redirects manually, and fails on any non-200.Verification
swift test— 3 new tests (symbol vs. named asset;resizable+ each content mode;AsyncImageURL incl. the nil case), 82 total passingAsyncImageloads a real remote photo, and "Load the other image" swaps in a different one, confirming the URL re-key[failed: …]rather than spinning foreverScope
AsyncImage'scontent/placeholderclosure overloads aren't included (this is the plainAsyncImage(url:)form), and there's no memory/disk cache — each composition of a new URL fetches. Named assets don't resolve on the desktop target, which has no resource bundle.